﻿using System;
using System.Linq;
using System.Windows.Forms;

namespace Xompare
{
    using System.Drawing;
    using System.IO;

    using FormsUtilities;

    using Utilities;

    using Xompare.Helpers;

    public partial class XmlFilesCompareControl : UserControl
    {
        /// <summary>
        /// Tracks the last size of the control.
        /// </summary>
        private Size m_lastSize = new Size(0, 0);

        /// <summary>
        /// Whether to compare. Used to mask comparison when not needed.
        /// </summary>
        private bool m_doCompare = true;

        /// <summary>
        /// Defines the event triggered when the compared files are chosen differently.
        /// </summary>
        /// <param name="newCompareName">The new comparison name.</param>
        public delegate void ComparedFilesChanged(string newCompareName);
        public event ComparedFilesChanged OnComparedFilesChanged;

        public XmlFilesCompareControl()
        {
            InitializeComponent();

            // Set viewers' properties:
            xmlFileTreeOrigin.FileViewerRole = FileComparisonRole.OriginSide;
            xmlFileTreeTarget.FileViewerRole = FileComparisonRole.TargetSide;

            xmlRichTextCompare.OriginText = string.Empty;
            xmlRichTextCompare.TargetText = string.Empty;

            Action comparedFilesChangedNotification =
                () =>
                {
                    if (!m_doCompare)
                    {
                        return;
                    }

                    string originalName = Path.GetFileName(NameAndLabel.GetSafeName(OriginalXmlPath));
                    string targetName = Path.GetFileName(NameAndLabel.GetSafeName(TargetXmlPath));

                    OnComparedFilesChanged(originalName + " <-> " + targetName);

                    if (!string.IsNullOrEmpty(originalName) && !string.IsNullOrEmpty(targetName))
                    {
                        // Compare the files:
                        var comparer = new CollectionComparer<string, ComparableSet<KeyedXmlRecord>>(
                            xmlFileTreeOrigin.PreParseInformation.TreeRecords, xmlFileTreeTarget.PreParseInformation.TreeRecords);

                        if (!comparer.OnlyOriginal.Any() && !comparer.OnlyTarget.Any() && !comparer.Differences.Any())
                        {
                            LogUi.LogInfo("Chosen files are identical.");
                        }

                        // Set the differences' information to the viewers:
                        xmlFileTreeOrigin.XmlOnlyHere = comparer.OnlyOriginal;
                        xmlFileTreeOrigin.XmlOnlyThere = comparer.OnlyTarget;
                        xmlFileTreeOrigin.XmlDifferences = comparer.Differences;
                        xmlFileTreeTarget.XmlOnlyHere = comparer.OnlyTarget;
                        xmlFileTreeTarget.XmlOnlyThere = comparer.OnlyOriginal;
                        xmlFileTreeTarget.XmlDifferences = comparer.Differences;

                        // Refesh the XML tree representation:
                        RefreshComparison();
                    }
                };

            xmlFileTreeOrigin.OnFilePathChanged += comparedFilesChangedNotification;
            xmlFileTreeTarget.OnFilePathChanged += comparedFilesChangedNotification;
            xmlFileTreeOrigin.OnDiffNodeSelected += HandleTreeSelection;
            xmlFileTreeTarget.OnDiffNodeSelected += HandleTreeSelection;
        }

        #region Public exported API

        /// <summary>
        /// Gets or sets the path to the XML file which is considered the original.
        /// </summary>
        public NameAndLabel OriginalXmlPath
        {
            get
            {
                return xmlFileTreeOrigin.FilePath;
            }

            set
            {
                xmlFileTreeOrigin.FilePath = value;
            }
        }

        /// <summary>
        /// Gets or sets the path to the XML file which is considered the target.
        /// </summary>
        public NameAndLabel TargetXmlPath
        {
            get
            {
                return xmlFileTreeTarget.FilePath;
            }

            set
            {
                xmlFileTreeTarget.FilePath = value;
            }
        }

        /// <summary>
        /// Set the comparison properties of the XML tree-views.
        /// </summary>
        /// <param name="ignoreNameCase">Whether to ignore XML objects' name casing. 'null' means do not change settings.</param>
        /// <param name="ignoreValueCase">Whether to ignore XML objects' value casing. 'null' means do not change settings.</param>
        /// <param name="ignoreWhiteSpaces">Whether to ignore white spaces on XML names and values. 'null' means do not change settings.</param>
        public void SetComparisonProps(bool? ignoreNameCase = null, bool? ignoreValueCase = null, bool? ignoreWhiteSpaces = null)
        {
            if (ignoreNameCase.HasValue)
            {
                xmlFileTreeOrigin.PreParseSettings.IgnoreNameCase = ignoreNameCase.Value;
                xmlFileTreeTarget.PreParseSettings.IgnoreNameCase = ignoreNameCase.Value;
            }

            if (ignoreValueCase.HasValue)
            {
                xmlFileTreeOrigin.PreParseSettings.IgnoreValueCase = ignoreValueCase.Value;
                xmlFileTreeTarget.PreParseSettings.IgnoreValueCase = ignoreValueCase.Value;
            }

            if (ignoreWhiteSpaces.HasValue)
            {
                xmlFileTreeOrigin.PreParseSettings.IgnoreWhiteSpaces = ignoreWhiteSpaces.Value;
                xmlFileTreeTarget.PreParseSettings.IgnoreWhiteSpaces = ignoreWhiteSpaces.Value;
            }
        }

        /// <summary>
        /// Reload the files at hand. If the reload operation is not forced - it will only happen if the comparison settings required
        /// are different than those already set.
        /// </summary>
        /// <param name="force"></param>
        /// <param name="ignoreNameCase">Whether to ignore XML objects' name casing. 'null' means do not change settings.</param>
        /// <param name="ignoreValueCase">Whether to ignore XML objects' value casing. 'null' means do not change settings.</param>
        /// <param name="ignoreWhiteSpaces">Whether to ignore white spaces on XML names and values. 'null' means do not change settings.</param>
        public void Reload(bool force, bool? ignoreNameCase = null, bool? ignoreValueCase = null, bool? ignoreWhiteSpaces = null)
        {
            if (force ||
                ((ignoreNameCase.HasValue && ignoreNameCase.Value != xmlFileTreeOrigin.PreParseSettings.IgnoreNameCase) ||
                 (ignoreValueCase.HasValue && ignoreValueCase.Value != xmlFileTreeOrigin.PreParseSettings.IgnoreValueCase) ||
                 (ignoreWhiteSpaces.HasValue && ignoreWhiteSpaces.Value != xmlFileTreeOrigin.PreParseSettings.IgnoreWhiteSpaces)))
            {
                SetComparisonProps(ignoreNameCase, ignoreValueCase, ignoreWhiteSpaces);

                m_doCompare = false;
                OriginalXmlPath = OriginalXmlPath;
                m_doCompare = true;
                TargetXmlPath = TargetXmlPath;
            }
        }

        /// <summary>
        /// Refresh the comparison view.
        /// </summary>
        public void RefreshComparison()
        {
            xmlFileTreeOrigin.RefreshXmlTree();
            xmlFileTreeTarget.RefreshXmlTree();
        }

        /// <summary>
        /// Things to do upon removal of the control.
        /// </summary>
        public void OnRemoval()
        {
            xmlFileTreeOrigin.OnRemoval();
            xmlFileTreeTarget.OnRemoval();
        }

        #endregion

        #region Private helpers

        /// <summary>
        /// Handles a selection change in the XML trees.
        /// </summary>
        /// <param name="sender">The XML tree control triggering the event.</param>
        /// <param name="treeNodeInfo">The tree-node information of the newly selected tree.</param>
        /// <param name="isControlPressed">Whether the Ctrl key was pressed at the time of the tree-node selection.</param>
        private void HandleTreeSelection(XmlFileTreeControl sender, NodeInformation treeNodeInfo, bool isControlPressed)
        {
            string originValueText = xmlRichTextCompare.OriginText;
            string targetValueText = xmlRichTextCompare.TargetText;

            if (sender.FileViewerRole == FileComparisonRole.OriginSide)
            {
                // For the original side:
                NodeInformation peerNodeInfo = null;

                if (!isControlPressed)
                {
                    peerNodeInfo = xmlFileTreeTarget.EnsureNodeVisible(treeNodeInfo);
                    if (peerNodeInfo == null)
                    {
                        targetValueText = string.Empty;
                    }
                }

                // Present the value of the selected tree-node:
                MiscUtils.ExecuteOnXmlObject(
                    treeNodeInfo.XmlObject,
                    element =>
                        {
                            originValueText = MiscUtils.GetElementSingleValue(element);
                            xmlRichTextCompare.OriginValueKind = XmlValueKind.ElementValue;
                        },
                    attribute =>
                        {
                            originValueText = attribute.Value;
                            xmlRichTextCompare.OriginValueKind = XmlValueKind.AttributeValue;
                        });

                if (peerNodeInfo != null)
                {
                    // Present the value of the peer (target-side) tree-node:
                    MiscUtils.ExecuteOnXmlObject(
                        peerNodeInfo.XmlObject,
                        element =>
                            {
                                targetValueText = MiscUtils.GetElementSingleValue(element);
                                xmlRichTextCompare.TargetValueKind = XmlValueKind.ElementValue;
                            },
                        attribute =>
                            {
                                targetValueText = attribute.Value;
                                xmlRichTextCompare.TargetValueKind = XmlValueKind.AttributeValue;
                            });
                }
            }
            else
            {
                // For the target side:
                NodeInformation peerNodeInfo = null;

                if (!isControlPressed)
                {
                    peerNodeInfo = xmlFileTreeOrigin.EnsureNodeVisible(treeNodeInfo);
                    if (peerNodeInfo == null)
                    {
                        originValueText = string.Empty;
                    }
                }

                // Present the value of the selected tree-node:
                MiscUtils.ExecuteOnXmlObject(
                    treeNodeInfo.XmlObject,
                    element =>
                        {
                            targetValueText = MiscUtils.GetElementSingleValue(element);
                            xmlRichTextCompare.TargetValueKind = XmlValueKind.ElementValue;
                        },
                    attribute =>
                        {
                            targetValueText = attribute.Value;
                            xmlRichTextCompare.TargetValueKind = XmlValueKind.AttributeValue;
                        });

                if (peerNodeInfo != null)
                {
                    // Present the value of the peer (original-side) tree-node:
                    MiscUtils.ExecuteOnXmlObject(
                        peerNodeInfo.XmlObject,
                        element =>
                            {
                                originValueText = MiscUtils.GetElementSingleValue(element);
                                xmlRichTextCompare.OriginValueKind = XmlValueKind.ElementValue;
                            },
                        attribute =>
                            {
                                originValueText = attribute.Value;
                                xmlRichTextCompare.OriginValueKind = XmlValueKind.AttributeValue;
                            });
                }
            }

            xmlRichTextCompare.AllowDifferenceColoring = false;

            xmlRichTextCompare.OriginText = originValueText.EliminateEols();
            xmlRichTextCompare.TargetText = targetValueText.EliminateEols();
            xmlRichTextCompare.AllowDifferenceColoring = true;
        }

        #endregion

        private void XmlFilesCompareControl_Resize(object sender, EventArgs e)
        {
            if (Size.Width > 0)
            {
                var widthRatio = Size.Width / (double)m_lastSize.Width;

                splitterXmlFiles.SplitPosition = (int)((splitterXmlFiles.SplitPosition + splitterXmlFiles.Width / 2) * widthRatio) - splitterXmlFiles.Width / 2;
                m_lastSize = Size;
            }
        }

        private void splitterXmlFiles_DoubleClick(object sender, EventArgs e)
        {
            splitterXmlFiles.SplitPosition = Width / 2 - splitterXmlFiles.Width / 2;
        }

        private void XmlFilesCompareControl_Load(object sender, EventArgs e)
        {
            m_lastSize = Size;
            splitterXmlFiles.SplitPosition = Width / 2 - splitterXmlFiles.Width / 2;
        }
    }
}
